package com.huawei.bitfun.insight.processor;

import com.alibaba.fastjson.JSONObject;
import com.huawei.bitfun.insight.database.DatabaseManager;
import com.huawei.bitfun.insight.processor.message.UploadFileResponse;
import com.huawei.bitfun.insight.type.DataSourceTable;
import com.huawei.bitfun.insight.database.ConnectionManager;
import com.huawei.bitfun.insight.processor.message.AddDatasourceRequest;
import com.huawei.bitfun.insight.processor.message.DeleteRequest;
import com.huawei.bitfun.insight.type.converter.DataSourceConverter;
import com.huawei.bitfun.insight.utils.Common;
import com.huawei.bitfun.insight.utils.DialogManager;
import com.huawei.bitfun.insight.utils.SqlStatements;
import com.huawei.bitfun.insight.utils.TraceFileType;
import com.huawei.deveco.insight.ohos.LangProps;
import com.huawei.deveco.insight.ohos.common.ProfilerError;
import com.huawei.deveco.insight.ohos.common.ProfilerException;
import com.huawei.deveco.insight.ohos.common.Response;
import com.huawei.deveco.insight.ohos.resourcehandler.controller.RequestMapping;
import com.huawei.deveco.insight.ohos.utils.BalloonNotification;
import com.huawei.deveco.insight.ohos.utils.DocumentUtils;
import com.huawei.deveco.insight.ohos.utils.ExecutableUtils;
import com.huawei.deveco.insight.ohos.utils.JsonUtil;
import com.huawei.deveco.insight.ohos.utils.SyncTaskUtils;
import com.huawei.deveco.insight.ohos.utils.SystemUtil;
import com.huawei.deveco.insight.ohos.utils.ValidateUtil;
import com.huawei.deveco.insight.ohos.utils.ZipUtil;
import com.intellij.notification.NotificationType;
import org.apache.commons.io.FilenameUtils;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

import static com.huawei.bitfun.insight.utils.TraceFileType.FTRACE_EXTENSION;
import static com.huawei.bitfun.insight.utils.TraceFileType.HTRACE_EXTENSION;


@RequestMapping(path = "datasource")
public class DataSourceProcessor {
    private static final Logger LOGGER = LoggerFactory.getLogger(DataSourceProcessor.class);

    private static final int MAX_TRACE_FILE_LENGTH = 524288000;
    private static List<DataSourceTable> list = new ArrayList<>();

    @RequestMapping(path = "get")
    public static Response<?> getDataSourceList(JSONObject params) {
        try {
            list = DatabaseManager.queryData(SqlStatements.GET_DATASOURCE_STATEMENT,
                    ConnectionManager.getBasicDataSource(), new DataSourceConverter()).getDataList();
            return Response.success(list);
        } catch (ProfilerException e) {
            return Response.failure(e.getInsightError());
        }
    }

    @RequestMapping(path = "add")
    public static Response<?> addDataSource(JSONObject params) {
        AddDatasourceRequest request = JsonUtil.parseObject(params, AddDatasourceRequest.class);
        ValidateUtil.validate(Objects.requireNonNull(request));
        try {
            String sourceFilePath = request.getPath();
            String sourceFileName = FilenameUtils.getName(sourceFilePath);
            String path = "";
            switch (TraceFileType.getTraceFileType(sourceFileName)) {
                case DB_EXTENSION -> path = copyFileToCache(sourceFilePath, Common.getUUID());
                case FTRACE_EXTENSION, HTRACE_EXTENSION ->
                        path = parseFile(sourceFilePath, Common.getUUID());
                case INSIGHT_EXTENSION -> path = parseInsightFile(sourceFilePath);
                case DEFAULT_EXTENSION -> {
                    LOGGER.warn("unknown file type: {}", sourceFileName);
                    return Response.failure(ProfilerError.SQL_ERROR);
                }
            }
            if (path == null) {
                return Response.failure(ProfilerError.IMPORT_ERROR);
            }
            boolean isSuccess = DatabaseManager.updateData(SqlStatements.ADD_DATASOURCE_STATEMENT,
                    ConnectionManager.getBasicDataSource(), request.getName(), path);
            if (isSuccess) {
                return Response.success("success");
            }
            return Response.failure(ProfilerError.SQL_ERROR);
        } catch (IOException e) {
            return Response.failure(ProfilerError.SQL_ERROR);
        }
    }

    @RequestMapping(path = "delete")
    public static Response<?> deleteDatasource(JSONObject params) {
        DeleteRequest request = JsonUtil.parseObject(params, DeleteRequest.class);
        ValidateUtil.validate(Objects.requireNonNull(request));
        try {
            boolean isSuccess = DatabaseManager.updateData(SqlStatements.DELETE_DATASOURCE_STATEMENT,
                    ConnectionManager.getBasicDataSource(), request.getId());
            if (isSuccess) {
                ConnectionManager.deleteDataSourceAndGetPath(request.getId());
                Optional<DataSourceTable> dataSourceTableOptional
                        = list.stream().filter(dataSourceTable -> dataSourceTable.getId()== request.getId()).findAny();
                dataSourceTableOptional.ifPresent(
                        dataSourceTable -> DocumentUtils.deleteDir(new File(dataSourceTable.getPath()).getParentFile()));
                return Response.success("success");
            }
        } catch (SQLException e) {
            LOGGER.warn("delete datasource failed.", e);
        }
        return Response.failure(ProfilerError.SQL_ERROR);
    }

    @RequestMapping(path = "upload")
    public static Response<?> uploadFile(JSONObject params) {
        String[] srcFilePath = new String[1];
        Optional<String[]> optionalPath = SyncTaskUtils.getSingleton().executeTask(new SyncTaskUtils.Task<>(srcFilePath) {
            @Override
            public void run() {
                String[] tmp = this.getResult();
                tmp[0] = DialogManager.showDialogAndGetFile();
            }
        }, srcFilePath, true);
        if (optionalPath.isPresent() && srcFilePath[0] != null) {
            srcFilePath = optionalPath.get();
        } else {
            LOGGER.warn("select file error.");
            return Response.failure(ProfilerError.IMPORT_ERROR);
        }
        UploadFileResponse response = new UploadFileResponse(FilenameUtils.getName(srcFilePath[0]), srcFilePath[0]);
        return Response.success(response);
    }

    private static @Nullable String parseInsightFile(String insightFilePath) throws IOException {
        // 复制文件到uuid目录下，然后重命名为zip文件并解压；
        // 解压完成后查找trace文件，使用trace_streamer解析trace文件
        String insightFileName = FilenameUtils.getName(insightFilePath);
        String uuid = Common.getUUID();
        String destPath = Common.getDestPath(uuid);
        DocumentUtils.createIfAbsent(destPath);

        String destZipName = FilenameUtils.getBaseName(insightFileName) + ".zip";
        String destZipPath = Common.joinWithForwardSlash(destPath, destZipName);
        DocumentUtils.copyFile(insightFilePath, destZipPath);
        if (!ZipUtil.extractZip(destZipPath, destPath)) {
            LOGGER.warn("unzip failed: {}", destZipPath);
            return null;
        }
        File destPathFile = new File(destPath);
        if (destPathFile.listFiles() == null) {
            LOGGER.warn("tmp dir can't be empty: {}", destPath);
            return null;
        }
        Optional<File> traceFile = Arrays.stream(destPathFile.listFiles()).filter((file ->
                FilenameUtils.isExtension(file.getName(), FTRACE_EXTENSION.toString(),
                        HTRACE_EXTENSION.toString()))).findAny();
        if (traceFile.isEmpty()) {
            LOGGER.warn("Can't find trace file in unzip path: {}", destPathFile);
            return null;
        }
        return parseFile(traceFile.get().getCanonicalPath(), uuid);
    }

    private static String parseFile(String traceFilePath, String destFileName) throws IOException {
        // 使用trace_streamer解析trace文件
        String sourceFileName = FilenameUtils.getName(traceFilePath);
        String dbFileName = FilenameUtils.getBaseName(sourceFileName) + "_trace.db";
        String destPath = Common.getDestPath(destFileName);
        DocumentUtils.createIfAbsent(destPath);
        String destDbPath = Common.joinWithForwardSlash(destPath, dbFileName);
        boolean success = parseTraceFile(new File(traceFilePath), dbFileName, destDbPath);
        if (!success) {
            LOGGER.warn("parse trace file failed: {}", traceFilePath);
            return null;
        }
        return destDbPath;
    }

    private static String copyFileToCache(String filePath, String destPathName) throws IOException {
        // 复制文件到profilerEx/文件名——时间目录
        String sourceFileName = FilenameUtils.getName(filePath);
        String destPath = Common.getDestPath(destPathName);
        DocumentUtils.createIfAbsent(destPath);
        String destFilePath = Common.joinWithForwardSlash(destPath, sourceFileName);
        DocumentUtils.copyFile(filePath, destFilePath);
        return destFilePath;
    }

    private static boolean parseTraceFile(File sourceFile, String dbName, String destFilePath) {
        try {
            String traceStreamerPath = ExecutableUtils.getTraceStreamerPath();
            if (!DocumentUtils.checkExist(traceStreamerPath)) {
                LOGGER.warn("Parse trace file failed, trace Streamer does not exist.");
                return false;
            }
            String traceLogPath = DocumentUtils.getLogFilePath(dbName + ".log");
            List<String> processArgs = new ArrayList<>();
            processArgs.add(traceStreamerPath);
            processArgs.add(sourceFile.getCanonicalPath());
            processArgs.add("-e");
            processArgs.add(destFilePath);
            LOGGER.info("TraceStreamer parseTraceFile begin.");
            if (!SystemUtil.execCommand(processArgs, true, traceLogPath)) {
                LOGGER.warn("Parse trace file failed, exec command error, log is {}", traceLogPath);
                return false;
            }
            LOGGER.info("TraceStreamer parseTraceFile end.");
            if (!DocumentUtils.checkExist(destFilePath)) {
                LOGGER.warn("Parse trace file failed, not found db, log is {}", traceLogPath);
                long fileLength = destFilePath.length();
                if (fileLength > MAX_TRACE_FILE_LENGTH) {
                    LOGGER.info("Parse trace file failed, file size is {}", fileLength);
                    BalloonNotification.show(LangProps.message("dlg.trace.parse.failed"), NotificationType.INFORMATION);
                }
                return false;
            }
        } catch (IOException e) {
            LOGGER.warn("Parse trace file failed, get file path error.");
            return false;
        }
        return true;
    }
}
